home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Symantec Visual Cafe for Java 2.5
/
symantec-visual-cafe-2.5-database-dev-edition.iso
/
Visual Cafe Pro v1.0
/
SOURCE.BIN
/
TreeView.java
< prev
next >
Encoding:
Amiga
Atari
Commodore
DOS
FM Towns/JPY
Macintosh
Macintosh JP
Macintosh to JP
NeXTSTEP
RISC OS/Acorn
Shift JIS
UTF-8
Wrap
Java Source
|
1997-06-19
|
37.3 KB
|
1,427 lines
package symantec.itools.awt;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Event;
import java.awt.FontMetrics;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.LayoutManager;
import java.awt.Scrollbar;
import java.awt.Panel;
import java.awt.Rectangle;
import java.util.Vector;
// 01/15/97 RKM Changed drawTree to make certain g1 has a font, before calling getFontMetrics on it
// 01/15/07 RKM Added invalidate to setTreeStructure
// 01/29/97 TWB Integrated changes from Windows and RKM's changes
// 01/29/97 TWB Integrated changes from Macintosh
// 02/05/97 MSH Changed so that draws from first visible node
// 02/27/97 MSH Merged change to add SEL_CHANGED
// 04/02/97 TNM Draw all vertical lines
// 04/14/97 RKM Changed bogus invalidates to repaint
// RKM Changed hard coded sbVWidth to use preferredSize.width
// RKM Changed getTreeStructure so it actually returned a representation of what was in the treeview
// RKM Rearranged a lot of stuff to get this to work
// RKM Changed g1.drawRect in drawTree to not overlap the scrollbar
// RKM Changed parseTreeStructure to not force a root node
// 05/02/97 RKM Add arg to addSibling so caller could control whether the sible was added as the last sibling or not
// RKM Changed insert to call addSibling with false when handling NEXT
// RKM Kept addSibling with two params for compatibility
public class TreeView
extends Panel
{
// constants for insertion
/**
* Insertion constant. Indicates that the new node is to be a child
* of the existing node.
*/
public static final int CHILD = 0;
/**
* Insertion constant. Indicates that the new node is to be the next
* sibling of the existing node.
*/
public static final int NEXT = CHILD + 1;
/**
* Insertion constant. Indicates that the new node is to be the last
* sibling of the existing node.
*/
public static final int LAST = CHILD + 2;
/**
* Event type ID indicating the selection has changed.
*/
public static final int SEL_CHANGED = 1006; //selection changed event
private TreeNode rootNode; // root node of tree
private TreeNode selectedNode; // highlighted node
private TreeNode topVisibleNode; // first node in window
Scrollbar sbV; // vertical scrollbar
int sbVPosition=0; // hold value of vertical scrollbar
int sbVWidth; // width of vertical scrollbar
long sbVTimer = -1; // time of last vert scrollbar event
private int count=0; // Number of nodes in the tree
private int viewCount=0;// Number of viewable nodes in the tree
// (A node is viewable if all of its
// parents are expanded.)
private Color bgHighlightColor = Color.blue; // selection bg color
private Color fgHighlightColor = Color.white; // selection fg color
private int viewHeight = 300;
private int viewWidth = 300; // pixel size of tree display
private int viewWidest = 0 ; // widest item displayable (for horz scroll)
int cellSize = 16; // size of node image
int clickSize = 8; // size of mouse toggle (plus or minus)
int imageInset = 3; // left margin of node image
int textInset = 6; // left margin for text
int textBaseLine= 3; // position of font baseline from bottom of cell
private FontMetrics fm; // current font metrics
long timeMouseDown; // save time of last mouse down (for double click)
int doubleClickResolution = 333; // double-click speed in milliseconds
/**
* Offscreen Image used for buffering the painting process.
*/
protected Image im1; // offscreen image
/**
* Offscreen graphics context used for buffering the painting process.
*/
protected Graphics g1 = null; // offscreen graphics context
// constructors
/**
* Constructs an empty default TreeView.
*/
public TreeView()
{
super.setLayout(new BorderLayout());
add("East", sbV = new Scrollbar(Scrollbar.VERTICAL));
}
/**
* Constructs a TreeView with the given node.
*/
public TreeView(TreeNode head)
{
this();
selectedNode = rootNode = head;
count=1;
}
/**
* Sets this component's background color.
* This is a standard Java AWT method which gets called to change
* the background color of this component.
*
* @param c the new background color
* @see #getBackground
* @see #setForeground
*/
public void setBackground(Color c)
{
super.setBackground(c);
repaint();
}
/**
* Determines the current background color.
* This is a standard Java AWT method which gets called to determine the
* current background color for this component. If the background color
* hasn't been explicity specified using the setBackground() method, the
* background color of this component's parent is returned.
*
* @return the current background color
* @see #setBackground
* @see #getForeground
*/
public Color getBackground()
{
return super.getBackground();
}
/**
* Sets this component's foreground color.
* This is a standard Java AWT method which gets called to change
* the foreground color of this component.
*
* @param c the new foreground color
* @see #getForeground
* @see #setBackground
*/
public void setForeground(Color c)
{
super.setForeground(c);
repaint();
}
/**
* Gets the current foreground color.
* This is a standard Java AWT method which gets called to determine
* the current foreground color for this component. If the foreground
* color has not been explicitly set using the setForeground() method,
* the foreground color of of this component's parent is returned.
*
* @return the current foreground color
* @see #setForeground
* @see #getBackground
*/
public Color getForeground()
{
return super.getForeground();
}
/**
* Sets the foreground selection hilite color.
* @param c the new foreground selection hilite color
* @see #getFgHilite
*/
public void setFgHilite(Color c)
{
fgHighlightColor = c;
repaint();
}
/**
* Gets the current foreground selection hilite color.
* @return the current foreground selection hilite color
* @see #setFgHilite
*/
public Color getFgHilite()
{
return fgHighlightColor;
}
/**
* Gets the current background selection hilite color.
* @return the current background selection hilite color
* @see #getBgHilite
*/
public void setBgHilite(Color c)
{
bgHighlightColor = c;
repaint();
}
/**
* Gets the current background selection hilite color.
* @return the current background selection hilite color
* @see #setBgHilite
*/
public Color getBgHilite()
{
return bgHighlightColor;
}
// Insert a new node relative to a node in the tree.
// position = CHILD inserts the new node as a child of the node
// position = NEXT inserts the new node as the next sibling
// position = LAST inserts the new node as the last sibling
/**
* Inserts a new node relative to an existing node in the tree.
* @param newNode the new node to insert into the tree
* @param relativeNode the existing node used for a position reference
* @param position where to insert the new node relative to relativeNode.
* Legal values are CHILD, NEXT and LAST.
* @see #CHILD
* @see #NEXT
* @see #LAST
* @see #append
*/
public void insert(TreeNode newNode, TreeNode relativeNode, int position)
{
if (newNode==null || relativeNode==null)
return;
if (exists(relativeNode)==false)
return;
switch (position)
{
case CHILD:
addChild(newNode, relativeNode);
break;
case NEXT:
addSibling(newNode, relativeNode, false);
break;
case LAST:
addSibling(newNode, relativeNode, true);
break;
default:
// invalid position
return;
}
}
/**
* Returns the "root" node. The root node is the top-most node
* in the tree hierarchy. All other nodes stem from that one.
* @return the root tree node
*/
public TreeNode getRootNode()
{
return rootNode;
}
/**
* Returns the total number of nodes in the tree.
*/
public int getCount()
{
return count;
}
/**
* Returns the total number of viewable nodes in the tree.
* A node is viewable if all of its parents are expanded.
*/
public int getViewCount()
{
return viewCount;
}
/**
* Determines if the given node is viewable.
* A node is viewable if all of its parents are expanded.
* @param node the node to check
* @return true if the node is visible, false if it is not
* @see #viewable(java.lang.String)
*/
boolean viewable(TreeNode node)
{
for (int i=0; i<viewCount; i++)
{
if (node == v.elementAt(i))
{
return true;
}
}
return false;
}
/**
* Determines if the node with the given text is viewable.
* A node is viewable if all of its parents are expanded.
* @param s the node text to find
* @return true if the node is visible, false if it is not
* @see #viewable(TreeNode)
*/
boolean viewable(String s)
{
if (s==null)
{
return false;
}
for (int i=0; i<viewCount; i++)
{
TreeNode tn = (TreeNode)v.elementAt(i);
if (tn.text != null)
{
if (s.equals(tn.text))
{
return true;
}
}
}
return false;
}
/**
* Determines if the given node is in the TreeView.
* @param node the node to check
* @return true if the node is in the TreeView, false if it is not
* @see #exists(java.lang.String)
*/
public boolean exists(TreeNode node)
{
recount();
for (int i=0; i<count; i++)
{
if (node == e.elementAt(i))
{
return true;
}
}
return false;
}
/**
* Determines if the node with the given text is in the TreeView.
* @param s the node text to find
* @return true if the node is in the TreeView, false if it is not
* @see #exists(TreeNode)
*/
public boolean exists(String s)
{
recount();
if (s==null)
{
return false;
}
for (int i=0; i<count; i++)
{
TreeNode tn = (TreeNode)e.elementAt(i);
if (tn.text != null)
{
if (s.equals(tn.text))
{
return true;
}
}
}
return false;
}
// add new node to level 0
/**
* Adds a new node at root level. If there is no root node, the given
* node is made the root node. If there is a root node, the given node
* is made a sibling of the root node.
* @param newNode the new node to add
* @see #insert
*/
public void append(TreeNode newNode)
{
if (rootNode==null)
{
rootNode=newNode;
selectedNode = rootNode;
count=1;
}
else
{
addSibling(newNode, rootNode, true);
}
}
void addChild(TreeNode newNode, TreeNode relativeNode)
{
if (relativeNode.child == null)
{
relativeNode.child = newNode;
newNode.parent = relativeNode;
count++;
}
else
{
addSibling(newNode, relativeNode.child, true);
}
relativeNode.numberOfChildren++;
}
void addSibling(TreeNode newNode, TreeNode siblingNode)
{
addSibling(newNode,siblingNode,true);
}
void addSibling(TreeNode newNode, TreeNode siblingNode, boolean asLastSibling)
{
if (asLastSibling)
{
//Find last sibling
TreeNode tempNode = siblingNode;
while (tempNode.sibling != null)
tempNode = tempNode.sibling;
tempNode.sibling = newNode;
}
else
{
//Insert the newNode below the siblingNode
newNode.sibling = siblingNode.sibling;
siblingNode.sibling = newNode;
}
//Set the parent of the new node to the parent of the sibling
newNode.parent = siblingNode.parent;
count++;
}
/**
* Removes the node with the given text from the TreeView.
* @param s the node text to find
* @return the TreeNode removed from this TreeView or null if not found
* @see #remove(TreeNode)
* @see #removeSelected
*/
public TreeNode remove(String s)
{
recount();
for (int i=0; i<count; i++)
{
TreeNode tn = (TreeNode)e.elementAt(i);
if (tn.text != null)
{
if (s.equals(tn.text))
{
remove(tn);
return tn;
}
}
}
return null;
}
/**
* Removes the currently selected node from the TreeView.
* @see #remove(TreeNode)
* @see #remove(java.lang.String)
*/
public void removeSelected()
{
if (selectedNode != null)
{
remove(selectedNode);
}
}
/**
* Removes the given node from the TreeView.
* @param node the node to remove
* @return the TreeNode removed from this TreeView or null if not found
* @see #remove(java.lang.String)
* @see #removeSelected
*/
public void remove(TreeNode node)
{
if (!exists(node))
{
return;
}
if (node == selectedNode)
{
int index = v.indexOf(selectedNode);
if (index == -1)
{ //not viewable
index = e.indexOf(selectedNode);
}
if (index > viewCount-1)
{
index = viewCount-1;
}
if (index>0)
{
changeSelection((TreeNode)v.elementAt(index-1));
}
else if (viewCount>0)
{
changeSelection((TreeNode)v.elementAt(1));
}
}
// remove node and its decendents
if (node.parent != null)
{
if (node.parent.child == node)
{
if (node.sibling != null)
{
node.parent.child = node.sibling;
}
else
{
node.parent.child = null;
node.parent.collapse();
}
}
else
{
TreeNode tn=node.parent.child;
while (tn.sibling != node)
{
tn = tn.sibling;
}
if (node.sibling != null)
{
tn.sibling = node.sibling;
}
else
{
tn.sibling = null;
}
}
}
else
{
if (node == rootNode)
{
if (node.sibling == null)
{
rootNode=null;
}
else
{
rootNode=node.sibling;
}
}
else
{
TreeNode tn = rootNode;
while (tn.sibling != node)
{
tn = tn.sibling;
}
if (node.sibling != null)
{
tn.sibling = node.sibling;
}
else
{
tn.sibling = null;
}
}
}
recount();
}
// print each node of TreeView beginning with node
/**
* Print out the text of each node in the TreeView beginning with
* the given node.
* @param node the first node to print
*/
public void printTree(TreeNode node)
{
if (node==null)
{
return;
}
System.out.println(node.text);
printTree(node.child);
printTree(node.sibling);
}
private Vector e; // e is vector of existing nodes
private void recount()
{
count = 0;
e = new Vector();
if (rootNode != null)
{
rootNode.depth=0;
traverse(rootNode);
}
}
private void traverse(TreeNode node)
{
count++;
e.addElement(node);
if (node.child != null)
{
node.child.depth = node.depth+1;
traverse(node.child);
}
if (node.sibling != null)
{
node.sibling.depth = node.depth;
traverse(node.sibling);
}
}
private Vector v; // v is vector of viewable nodes
private void resetVector()
{
// Traverses tree to put nodes into vector v
// for internal processing. Depths of nodes are set,
// and viewCount and viewWidest is set.
v = new Vector(count);
viewWidest=30;
if (count < 1)
{
viewCount = 0;
return;
}
rootNode.depth=0;
vectorize(rootNode,true,v);
viewCount = v.size();
}
private void vectorize
(TreeNode node,
boolean respectExpanded,
Vector nodeVector)
{
if (node == null)
return;
nodeVector.addElement(node);
if ((!respectExpanded && node.child != null) || node.isExpanded())
{
node.child.depth = node.depth + 1;
vectorize(node.child,respectExpanded,nodeVector);
}
if (node.sibling != null)
{
node.sibling.depth = node.depth;
vectorize(node.sibling,respectExpanded,nodeVector);
}
}
private void debugVector()
{
int vSize = v.size();
for (int i=0; i<count; i++)
{
TreeNode node = (TreeNode) v.elementAt(i);
System.out.println(node.text);
}
}
// -----------------------------------------
// --------- event related methods ---------
// -----------------------------------------
/**
* Processes events for this component.
* This is a standard Java AWT method which gets called by the AWT
* to handle this component's events. The default handler for
* components dispatches to one of the following methods as needed:
* action(), gotFocus(), lostFocus(), keyDown(), keyUp(), mouseEnter(),
* mouseExit(), mouseMove(), mouseDrag(), mouseDown(), or mouseUp().
*
* @param event the event to handle
* @return true if the event was handled and no further action is needed,
* false to pass the event to this component's parent
* @see java.awt.Component#action
* @see java.awt.Component#gotFocus
* @see java.awt.Component#lostFocus
* @see #keyDown
* @see java.awt.Component#keyUp
* @see java.awt.Component#mouseEnter
* @see java.awt.Component#mouseExit
* @see java.awt.Component#mouseMove
* @see java.awt.Component#mouseDrag
* @see #mouseDown
* @see java.awt.Component#mouseUp
*/
public boolean handleEvent(Event event)
{
if (event.target == sbV)
{
if (event.arg == null)
{
return false;
}
if (sbVPosition != ((Integer) event.arg).intValue())
{
sbVPosition = ((Integer) event.arg).intValue();
redraw();
}
}
return(super.handleEvent(event));
}
/**
* Processes MOUSE_DOWN events.
* This is a standard Java AWT method which gets called by the AWT
* method handleEvent() in response to receiving a MOUSE_DOWN
* event. These events occur when the mouse button is pressed while
* inside this component.
*
* @param event the event
* @param x the component-relative horizontal coordinate of the mouse
* @param y the component-relative vertical coordinate of the mouse
*
* @return true if the event was handled
*
* @see java.awt.Component#mouseUp
* @see #handleEvent
*/
public boolean mouseDown(Event event, int x, int y)
{
int index = (y/cellSize) + sbVPosition;
if (index > viewCount-1)
{
return false; //clicked below the last node
}
TreeNode oldNode = selectedNode;
TreeNode newNode = (TreeNode)v.elementAt(index);
int newDepth = newNode.getDepth();
changeSelection(newNode);
// check for toggle box click
// todo: make it a bit bigger
Rectangle toggleBox = new Rectangle(cellSize*newDepth + cellSize/4,
(index-sbVPosition)*cellSize + clickSize/2,
clickSize, clickSize);
if (toggleBox.inside(x,y))
{
newNode.toggle();
sendActionEvent(event);
redraw();
}
else
{
// check for double click
long currentTime = event.when;
if ((newNode==oldNode) && ((event.when-timeMouseDown)<doubleClickResolution))
{
newNode.toggle();
redraw();
sendActionEvent(event);
return false;
}
else
{
//single click action could be added here
timeMouseDown = event.when;
}
}
return true;
}
/**
* Processes KEY_PRESS and KEY_ACTION events.
* This is a standard Java AWT method which gets called by the AWT
* method handleEvent() in response to receiving a KEY_PRESS or
* KEY_ACTION event. These events occur when this component has the focus
* and the user presses a "normal" or an "action" (F1, page up, etc) key.
*
* @param event the Event
* @param key the key that was pressed
* @return true if the event was handled
* @see java.awt.Component#keyUp
* @see #handleEvent
*/
public boolean keyDown(Event event, int key)
{
int index = v.indexOf(selectedNode);
switch (key)
{
case 10: //enter key
sendActionEvent(event);
requestFocus();
break;
case Event.LEFT: //left arrow
if (selectedNode.isExpanded())
{
selectedNode.toggle();
redraw();
break;
}
// else drop through to "UP" with no "break;"
case Event.UP:
if (index > 0)
{
index--;
changeSelection((TreeNode)v.elementAt(index));
requestFocus();
}
break;
case Event.RIGHT:
if (selectedNode.isExpandable() && (!selectedNode.isExpanded()))
{
selectedNode.toggle();
sendActionEvent(event);
redraw();
break;
}
if (!selectedNode.isExpandable())
{
break;
}
// else drop thru' to DOWN
case Event.DOWN:
if (index < viewCount-1)
{
index++;
changeSelection((TreeNode)v.elementAt(index));
requestFocus();
}
break;
}
return false;
}
private void sendActionEvent(Event event)
{
int id = event.id;
Object arg = event.arg;
event.id = Event.ACTION_EVENT;
event.arg = new String(selectedNode.getText());
postEvent(event);
event.id = id;
event.arg = arg;
}
/**
* Returns the currently selected node.
*/
public TreeNode getSelectedNode()
{
return selectedNode;
}
/**
* Gets the text of the currently selected node.
* @return the text of the currently selected node or null if no node
* is selected
*/
public String getSelectedText()
{
if (selectedNode==null)
{
return null;
}
return selectedNode.getText();
}
private void changeSelection(TreeNode node)
{
if (node == selectedNode)
{
return;
}
TreeNode oldNode = selectedNode;
selectedNode = node;
drawNodeText(oldNode, (v.indexOf(oldNode) - sbVPosition)*cellSize, true);
drawNodeText(node, (v.indexOf(node) - sbVPosition)*cellSize, true);
// send select event
int index = v.indexOf(selectedNode);
postEvent(new Event(this, SEL_CHANGED, selectedNode));
if (index < sbVPosition)
{ //scroll up
sbVPosition--;
sbV.setValue(sbVPosition);
redraw();
return;
}
if (index >= sbVPosition + (viewHeight-cellSize/2)/cellSize)
{
sbVPosition++;
sbV.setValue(sbVPosition);
redraw();
return;
}
repaint();
}
// -----------------------------------------
// --------- graphics related methods ------
// -----------------------------------------
/**
* Handles redrawing of this component on the screen.
* This is a standard Java AWT method which gets called by the Java
* AWT (repaint()) to handle repainting this component on the screen.
* The graphics context clipping region is set to the bounding rectangle
* of this component and its <0,0> coordinate is this component's
* top-left corner.
* Typically this method paints the background color to clear the
* component's drawing space, sets graphics context to be the foreground
* color, and then calls paint() to draw the component.
*
* It is overridden here to reduce flicker by eliminating the uneeded
* clearing of the background.
*
* @param g the graphics context
* @see java.awt.Component#repaint
* @see #paint
*/
public void update (Graphics g)
{
//(eliminates background draw to reduce flicker)
paint(g);
}
/**
* Paints this component using the given graphics context.
* This is a standard Java AWT method which typically gets called
* by the AWT to handle painting this component. It paints this component
* using the given graphics context. The graphics context clipping region
* is set to the bounding rectangle of this component and its <0,0>
* coordinate is this component's top-left corner.
*
* @param g the graphics context used for painting
* @see java.awt.Component#repaint
* @see #update
*/
public void paint (Graphics g)
{
Dimension d = size();
if ((d.width != viewWidth) || (d.height != viewHeight))
{
// size has changed, must redraw image
redraw();
//return;
}
else if (symantec.beans.Beans.isDesignTime())
{
resetVector();
layout();
drawTree();
}
g.drawImage(im1, 0, 0, this);
}
/**
* Lays out the vertical scrollbar as needed, then draws the TreeView into
an offscreen image. This is used for cleaner refresh.
*/
public void redraw()
{
resetVector();
if (viewCount > viewHeight/cellSize)
{
// need the vertical scrollbar
sbV.setValues(sbVPosition, (viewHeight/cellSize), 0, viewCount-2);
sbV.setPageIncrement(1);
sbVWidth = sbV.preferredSize().width;
getParent().paintAll(getParent().getGraphics());
sbV.show();
layout();
}
else
{
sbV.hide();
sbVWidth = 1;
sbVPosition = 0;
layout();
}
drawTree();
repaint();
}
/**
* Draws the TreeView into an offscreen image. This is used for cleaner refresh.
*/
public void drawTree()
{
Dimension d = size();
if ((d.width != viewWidth) || (d.height != viewHeight) || (g1==null))
{
// size has changed, must resize image
im1 = createImage(d.width, d.height);
if (g1 != null) {
g1.dispose();
}
g1 = im1.getGraphics();
viewWidth=d.width;
viewHeight=d.height;
}
Font f = getFont(); //unix version might not provide a default font
//Make certain there is a font
if (f == null)
{
f = new Font("TimesRoman", Font.PLAIN, 13);
g1.setFont(f);
setFont(f);
}
//Make certain the graphics object has a font (Mac doesn't seem to)
if (f != null)
{
if (g1.getFont() == null)
g1.setFont(f);
}
fm = g1.getFontMetrics();
g1.setColor(getBackground());
g1.fillRect(0,0,viewWidth,viewHeight); // clear image
//do drawing for each visible node
int lastOne=sbVPosition+viewHeight/cellSize+1;
if (lastOne > viewCount)
{
lastOne = viewCount;
}
TreeNode outerNode = (TreeNode)v.elementAt(sbVPosition);
for (int i=sbVPosition; i<lastOne; i++)
{
TreeNode node=(TreeNode)v.elementAt(i);
int x = cellSize*(node.depth + 1);
int y = (i-sbVPosition)*cellSize;
// draw lines
g1.setColor(getForeground());
// draw vertical sibling line
if (node.sibling != null)
{
int k = v.indexOf(node.sibling) - i;
if (k > lastOne)
{
k = lastOne;
}
drawDotLine(x - cellSize/2, y + cellSize/2,
x - cellSize/2, y + cellSize/2 + k*cellSize);
}
// if sibling is above page, draw up to top of page for this level
for (int m=0; m<i; m++)
{
TreeNode sib = (TreeNode)v.elementAt(m);
if ((sib.sibling == node) && (m<sbVPosition))
{
drawDotLine (x - cellSize/2, 0,
x - cellSize/2, y + cellSize/2);
}
}
// draw vertical child lines
if (node.isExpanded())
{
drawDotLine(x + cellSize/2, y + cellSize -2 ,
x + cellSize/2, y + cellSize + cellSize/2);
}
// draw node horizontal line
g1.setColor(getForeground());
drawDotLine(x - cellSize/2, y + cellSize/2,
x + cellSize/2, y + cellSize/2);
// draw toggle box
if (node.isExpandable())
{
g1.setColor(getBackground());
g1.fillRect(cellSize*(node.depth) + cellSize/4, y + clickSize/2, clickSize, clickSize );
g1.setColor(getForeground());
g1.drawRect(cellSize*(node.depth) + cellSize/4, y + clickSize/2, clickSize, clickSize );
// cross hair
g1.drawLine(cellSize*(node.depth) + cellSize/4 +2, y + cellSize/2,
cellSize*(node.depth) + cellSize/4 + clickSize -2, y + cellSize/2);
if (!(node.isExpanded()))
{
g1.drawLine(cellSize*(node.depth) + cellSize/2, y + clickSize/2 +2,
cellSize*(node.depth) + cellSize/2, y + clickSize/2 + clickSize -2);
}
}
// draw node image
Image nodeImage = node.getImage();
if (nodeImage != null)
{
g1.drawImage(nodeImage, x+imageInset, y, this);
}
// draw node text
if (node.text != null)
{
drawNodeText(node, y, node==selectedNode);
}
if(outerNode.depth>node.depth)
outerNode = node;
}
// draw outer vertical lines
while((outerNode = outerNode.parent) != null)
{
if(outerNode.sibling != null)
drawDotLine (cellSize*(outerNode.depth + 1) - cellSize/2, 0,
cellSize*(outerNode.depth + 1) - cellSize/2, d.height);;
}
// draw border
g1.setColor(getForeground());
g1.drawRect(0,0,viewWidth - sbVWidth, viewHeight - 1);
}
private void drawNodeText(TreeNode node, int yPosition, boolean eraseBackground)
{
Color fg, bg;
int depth=node.depth;
Image nodeImage = node.getImage();
int textOffset = ((depth + 1) * (cellSize)) + cellSize + textInset - (nodeImage==null ? 12:0);
if (node==selectedNode)
{
fg=fgHighlightColor;
bg=bgHighlightColor;
}
else
{
fg = getForeground();
bg = getBackground();
}
if (eraseBackground)
{
g1.setColor(bg);
g1.fillRect(textOffset-1, yPosition+1, fm.stringWidth(node.text)+4, cellSize-1);
}
g1.setColor(fg);
g1.drawString(node.text, textOffset, yPosition + cellSize - textBaseLine);
}
private void drawDotLine(int x0, int y0, int x1, int y1)
{
if (y0==y1)
{
for (int i = x0; i<x1; i+=2)
{
g1.drawLine(i,y0, i, y1);
}
}
else
{
for (int i = y0; i<y1; i+=2)
{
g1.drawLine(x0, i, x1, i);
}
}
}
/**
* Initializes the TreeView from a String array.
* There is one string for each node in the array. That string
* contains the text of the node indented with same number of
* leading spaces as the depth of that node in the tree.
* @param s the String array used for initialization
* @see #getTreeStructure
*/
public void setTreeStructure(String s[])
{
rootNode = selectedNode = null;
try
{
parseTreeStructure(s);
}
catch(InvalidTreeNodeException e)
{
System.out.println(e);
}
invalidate();
}
/**
* Gets a String array that reflects the current TreeView's contents.
* There is one string for each node in the array. That string
* contains the text of the node indented with same number of
* leading spaces as the depth of that node in the tree.
* @return the String array that reflects the TreeView's contents
* @see #setTreeStructure
*/
public String[] getTreeStructure()
{
//Create a vector representing current tree structure
Vector nodesVector = new Vector(count);
rootNode.depth = 0;
vectorize(rootNode,false,nodesVector);
//Convert this to a String[]
int numNodes = nodesVector.size();
String[] treeStructure = new String[numNodes];
for (int i = 0;i < numNodes;i++)
{
TreeNode thisNode = (TreeNode)nodesVector.elementAt(i);
//Add appropriate number of blanks
String treeString = "";
for (int numBlanks = 0;numBlanks < thisNode.depth;numBlanks++)
treeString += ' ';
//Add tree
treeString += thisNode.text;
//Put string into array
treeStructure[i] = treeString;
}
return treeStructure;
}
private void parseTreeStructure(String tempStructure[])
throws InvalidTreeNodeException
{
for(int i = 0; i < tempStructure.length; i++)
{
String entry = tempStructure[i];
int indentLevel = findLastPreSpace(entry)/*+1*/;
if (indentLevel == -1)
throw new InvalidTreeNodeException();
if (rootNode == null)
{
if (indentLevel != 0)
throw new InvalidTreeNodeException();
TreeNode node = new TreeNode(entry.trim());
node.setDepth(indentLevel);
append(node);
}
else
{
TreeNode currentNode = rootNode;
for(int j = 1; j < indentLevel; j++)
{
int numberOfChildren = currentNode.numberOfChildren;
TreeNode tempNode = null;
if (numberOfChildren > 0)
{
tempNode = currentNode.child;
while(tempNode.sibling != null)
tempNode = tempNode.sibling;
}
if(tempNode != null)
currentNode = tempNode;
else
break;
}
int diff = indentLevel - currentNode.getDepth();
if (diff > 1)
throw new InvalidTreeNodeException();
TreeNode node = new TreeNode(entry.trim());
node.setDepth(indentLevel);
if (diff == 1)
insert(node, currentNode, CHILD);
else
insert(node, currentNode, NEXT);
}
}
}
private int findLastPreSpace(String s)
{
int length;
length = s.length();
if(s.charAt(0) != ' ' && s.charAt(0) != '\t')
{
return 0;
}
for(int i = 1; i < length; i++)
{
if(s.charAt(i) != ' ' && s.charAt(i) != '\t')
{
return i;
}
}
return -1;
}
/**
* Returns the recommended dimensions to properly display this component.
* This is a standard Java AWT method which gets called to determine
* the recommended size of this component.
*
* @see #minimumSize
*/
public synchronized Dimension preferredSize()
{
//???RKM??? This is bogus, shouldn't this have something to do with the data ???
return new Dimension(175, 125);
}
/**
* Returns the minimum dimensions to properly display this component.
* This is a standard Java AWT method which gets called to determine
* the minimum size of this component.
*
* @see #preferredSize
*/
public synchronized Dimension minimumSize()
{
return new Dimension(50, 50);
}
/**
* Takes no action.
* This is a standard Java AWT method which gets called to specify
* which layout manager should be used to layout the components in
* standard containers.
*
* Since layout managers CANNOT BE USED with this container the standard
* setLayout has been OVERRIDDEN for this container and does nothing.
*
* @param lm the layout manager to use to layout this container's components
* (IGNORED)
* @see java.awt.Container#getLayout
**/
public void setLayout(LayoutManager lm)
{
}
}
class InvalidTreeNodeException
extends Exception
{
}